import { Inject, Injectable } from '@nestjs/common';
import axios from 'axios';
import { config } from 'config';
import Redis from 'ioredis';
import {
    ADDKEY_COMMAND,
    BALANCE_COMMAND,
    CANCEL_COMMAND,
    Keys,
    NOBODY_COMMAND,
    REFERRAL_COMMAND,
    REFILL_BALANCE_COMMAND,
    TRADER_COMMAND,
    USER_COMMAND,
    UserTelegram,
    optionForNobody,
    optionForUser,
    optionMenu,
    welcomeLetterForNobody,
    welcomeLetterForUser,
} from 'model';
import TelegramBot from 'node-telegram-bot-api';

@Injectable()
export class TelegramBotCommandService {
    constructor(
        @Inject('TelegramBot')
        private bot: TelegramBot,
        @Inject('Redis')
        private redis: Redis
    ) {}

    /**
     * Функция запрос отправка на сервер POST запрос с получением ответа
     * @param data - некий объект данных, требуемых для норм запроса на сервере
     * @param url - адрес запроса (без api)
     * @returns
     */
    private async reqPost(data: object, url: string) {
        try {
            const response = await axios.post(`${config.url.server}/api${url}`, data);
            return response.data;
        } catch (error) {
            console.log(`Request POST ending with ${error}`);
            return new Error('Request POST returned an error');
        }
    }

    /**
     * Функция запрос отправка на сервер PATCH запрос с получением ответа
     * @param data - некий объект данных, требуемых для норм запроса на сервере
     * @param url - адрес запроса (без api)
     * @returns
     */
    private async reqPatch(data: object, url: string) {
        try {
            const response = await axios.patch(`${config.url.server}/api${url}`, data);
            return response.data;
        } catch (error) {
            console.log(`Request PATCH ending with ${error}`);
            return new Error('Request PATCH returned an error');
        }
    }

    /**
     * Функция открывающая в зависимости от роли пользователя соответсвующее меню
     * В будущем если роли появятся то просто в switch помещается новая роль и ее меню
     * @param chatId
     * @param userId
     * @param text то что вы хотите написать в чат
     */
    private async openMenuKeyboard(chatId: string, userId: string, text: string) {
        switch (await this.redis.get(`id-${chatId}`)) {
            case 'null':
                await this.bot.sendMessage(chatId, text, optionMenu(NOBODY_COMMAND));
                break;
            case 'user':
                await this.bot.sendMessage(chatId, text, optionMenu(USER_COMMAND));
                break;
            case 'trader':
                await this.bot.sendMessage(chatId, text, optionMenu(TRADER_COMMAND));
                break;
        }
    }

    /**
     * Функция отправки ключей на сервер для проверки на валидность, в конечном итоге
     * в чат пишется итог и сбрасываются redis.keys этого пользователя
     * @param userId
     * @param chatId
     * @param keys пара ключей, экземпляр класса Keys
     */
    private async checkApiKey(userId: string, chatId: string, keys: Keys) {
        if (keys.apiKey && keys.secretKey) {
            const answerServer = await this.reqPatch(
                {
                    telegramId: userId,
                    apiKey: keys.apiKey,
                    secretKey: keys.secretKey,
                },
                '/user/login'
            );

            await this.openMenuKeyboard(chatId, userId, answerServer ? 'Keys are correct' : 'keys are invalid');
            await this.redis.del(`keys-id-${userId}`);
            const answer = await this.reqPost({ id: userId }, '/user/auth');
            await this.startMessage(chatId, userId, answer);
        } else {
            this.bot.sendMessage(chatId, 'Key entered successfully', optionMenu(ADDKEY_COMMAND));
        }
    }

    /**
     * Функция обрабатывающая введеные ключи в зависимости от action
     * @param chatId
     * @param userId
     * @param key
     */
    private async confirmKey(chatId: string, userId: string, key: string) {
        const keysJS: string | null = await this.redis.get(`keys-id-${userId}`);
        const mode = await this.redis.get(`action-id-${userId}`);

        if (keysJS) {
            const keys: Keys = JSON.parse(keysJS);
            keys[mode === 'apiKey' ? 'apiKey' : 'secretKey'] = key || '';
            await this.redis.set(`keys-id-${userId}`, JSON.stringify(keys), 'EX', 3600);
            await this.checkApiKey(userId, chatId, keys);
        } else {
            const data = mode === 'apiKey' ? { apiKey: key, secretKey: '' } : { apiKey: '', secretKey: key };
            await this.redis.set(`keys-id-${userId}`, JSON.stringify(data), 'EX', 3600);
            this.bot.sendMessage(chatId, 'Key entered successfully', optionMenu(ADDKEY_COMMAND));
        }
        await this.redis.del(`action-id-${userId}`);
    }

    /**
     * Функция триггер, срабатывает при любом взамодействии бота.
     * То есть если ботом активно пользоваться, то в redis будет всегда храниться роль и не обновляться (если не тыкать /start),
     * если пользователь забьет на бота, то запись о нем удалится через 2 часа,
     * потом в любой момент как пользователь зайдет, то keyboardTG с прошлого сеанса сохранится,
     * а при любом клике keyboardTG, автоматически подгрузится роль с помощью триггера
     * @param userId
     */
    async triggerCheckRole(userId: string) {
        try {
            if (!(await this.redis.get(`id-${userId}`))) {
                const answerServer = await this.reqPost({ id: userId }, '/user/auth');
                await this.redis.set(`id-${userId}`, answerServer.toString().toLowerCase(), 'EX', 7200);
            }
        } catch (error) {
            console.log(error);
            await this.openMenuKeyboard(userId, userId, 'An error occurred, please try again');
        }
    }

    private async startMessage(chatId: string, userId: string, answerServer: string | null) {
        switch (answerServer) {
            case 'nobody':
                await this.redis.set(`id-${userId}`, 'null', 'EX', 7200);
                this.bot
                    .sendMessage(chatId, welcomeLetterForNobody, optionForNobody())
                    .then((res) =>
                        this.bot.sendMessage(chatId, 'List command show down', optionMenu(NOBODY_COMMAND)).then()
                    );
                break;
            case 'user':
                await this.redis.set(`id-${userId}`, answerServer.toString().toLowerCase(), 'EX', 7200);
                this.bot
                    .sendMessage(chatId, welcomeLetterForUser, optionForUser())
                    .then((res) =>
                        this.bot.sendMessage(chatId, 'List command show down', optionMenu(USER_COMMAND)).then()
                    );
                break;
            case 'trader':
                await this.redis.set(`id-${userId}`, answerServer.toString().toLowerCase(), 'EX', 7200);
                this.bot
                    .sendMessage(chatId, 'Hello trader')
                    .then((res) =>
                        this.bot.sendMessage(chatId, 'List command show down', optionMenu(TRADER_COMMAND)).then()
                    );
                break;
            default:
                await this.bot.sendMessage(chatId, 'Sorry error');
                throw new Error('Unknow role');
        }
    }

    /**
     * Отправляет запрос на сервер, и получает роль, эту роль вписывает в Redis под определенным ключем (Про ключи в redis.instruction.md),
     * И пишет пользователю определенное сообщение с соответствующей для его роли клавиатуры (кнопочки)
     * @param chatId Id чата
     * @param user telegramId, firstName, lastName, username, refferalCode : all string
     */
    async commandStart(chatId: string, user: UserTelegram) {
        try {
            const answerServer = await this.reqPost(user, '/bot');
            await this.startMessage(chatId, user.telegramId, answerServer['result']);
        } catch (error) {
            console.log(`Telegram Error with ${error}`);
            await this.openMenuKeyboard(chatId, user.telegramId, 'An error occurred, please try again');
        }
    }

    ////////////////////////////////////// КЛЮЧИ ////////////////////////////////////////
    /**
     * Функция, открывающая меню команд добавления ключей, также имеет возможность показать уже ранее веденные ключи в зацензуренном виде
     * (если пользователь их не откатил, либо не прошло 2 часа)
     * @param chatId
     * @param userId
     */
    async goToMenuKeys(chatId: string, userId: string) {
        await this.bot.sendMessage(chatId, 'Choose what you want to enter', optionMenu(ADDKEY_COMMAND));
        const keysJS = await this.redis.get(`keys-id-${userId}`);
        if (keysJS) {
            const keys: Keys = JSON.parse(keysJS);
            if (keys.apiKey) {
                await this.bot.sendMessage(chatId, `Your apiKey ||${keys.apiKey}||`, { parse_mode: 'MarkdownV2' });
            } else {
                await this.bot.sendMessage(chatId, `Your secretKey ||${keys.secretKey}||`, {
                    parse_mode: 'MarkdownV2',
                });
            }
        }
    }

    /**
     * Функция которая одновременно и открывает меню с вводом ключа и заносит ключ в redis в зависимости от action и mode
     * При клике на Add ... key выставлять action = false, так как необходимо просмотреть следующее сообщение введеное именно пользователем
     * Если action существует то выставлять true
     * @param chatId
     * @param userId
     * @param mode какой ключ необходимо ввести
     * @param action режим работы если в чате появляется Enter the Key то выставлять true
     * @param text опциональное, можно вводить, можно и нет, если action = false, то просто учитываться не будет, а так text - введенный ключ user
     */
    async addKey(chatId: string, userId: string, mode: 'apiKey' | 'secretKey', action: boolean, text?: string) {
        if (!action) {
            await this.redis.set(`action-id-${userId}`, mode, 'EX', 300);
            await this.bot.sendMessage(
                chatId,
                mode === 'apiKey' ? 'Enter the Api Key' : 'Enter the Secret Key',
                optionMenu(CANCEL_COMMAND)
            );
        } else {
            await this.confirmKey(chatId, userId, text);
        }
    }
    ///////////////////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////// ОТМЕНЫ ////////////////////////////////////////
    async backToMenu(chatId: string, userId: string) {
        await this.openMenuKeyboard(chatId, userId, 'Go to main menu');
    }

    /**
     * Фукнция откатывает текущий action пользователя, тем самым любой введеный текст не будет считаться "аля ключ"
     * @param chatId
     * @param userId
     */
    async cancelOneOperation(chatId: string, userId: string) {
        switch (await this.redis.get(`action-id-${userId}`)) {
            case 'apiKey': // Я бы запихал сюда и secretKey, но синтаксис запрещает
                await this.redis.del(`action-id-${userId}`);
                this.goToMenuKeys(chatId, userId);
                break;
            case 'secretKey':
                await this.redis.del(`action-id-${userId}`);
                this.goToMenuKeys(chatId, userId);
                break;
            case 'refill':
                await this.redis.del(`action-id-${userId}`);
                this.goToMenuBalance(chatId, userId);
                break;
            case 'refCode':
                await this.redis.del(`action-id-${userId}`);
                this.goToMenuRefferal(chatId, userId);
                break;
            case 'setNick':
                await this.redis.del(`action-id-${userId}`);
                this.openMenuKeyboard(chatId, userId, 'Go to menu');
                break;
            case 'signal':
                await this.redis.del(`action-id-${userId}`);
                this.openMenuKeyboard(chatId, userId, 'Go to menu');
                break;
        }
    }

    /**
     * Фукнция сбрасывает все ранее веденные в рамках бота ключи, и показывает пользовательское меню команд
     * @param chatId
     * @param userId
     */
    async cancelKeys(chatId: string, userId: string) {
        await this.redis.del(`keys-id-${userId}`);
        await this.redis.del(`action-id-${userId}`);
        await this.openMenuKeyboard(chatId, userId, 'Go to main menu');
    }
    ///////////////////////////////////////////////////////////////////////////////////////

    ////////////////////////////////////// БАЛАНС ////////////////////////////////////////
    async goToMenuBalance(chatId: string, userId: string) {
        await this.bot.sendMessage(chatId, 'Choose what you want to enter', optionMenu(BALANCE_COMMAND));
    }

    // один раз был баг, что сервер выдал 503 ошибку и пользователю выдал Your balance undefined хз как с этим бороться
    // Сделал try catch и по идее в reqPost в случае ошибки выплевывается ошибка, а значит должен отработать tryCatch
    // Проверить сложно так как специально эта ошибка не выходит
    async checkBalance(chatId: string, userId: string) {
        try {
            const userBalance = await this.reqPost({ id: userId }, '/user/balance');
            await this.bot.sendMessage(chatId, `Your balance - ${userBalance}`, optionMenu(BALANCE_COMMAND));
        } catch (error) {
            console.log(error);
            await this.bot.sendMessage(chatId, 'An error occurred, please try again', optionMenu(BALANCE_COMMAND));
        }
    }

    /**
     * Кринжовая проверка вхождения суммы от пользователя в наличие текста в кнопках
     * Можно это делать в service, но не хочется много импортов там где не надо
     * @param chatId
     * @param userId
     * @param amount
     */
    async refillBalance(chatId: string, userId: string, amount?: string) {
        try {
            if ((await this.redis.get(`action-id-${userId}`)) === 'refill') {
                const isIncluded = REFILL_BALANCE_COMMAND.some((item) => {
                    return item.some((subItem) => {
                        return subItem.text === amount;
                    });
                });
                if (isIncluded) {
                    const answerServer = await this.reqPost({ telegramId: userId, amount: amount }, '/user/refill');
                    await this.openMenuKeyboard(
                        chatId,
                        userId,
                        answerServer ? 'Payment was successful' : 'Payment canceled'
                    );
                    await this.redis.del(`action-id-${userId}`);
                } else {
                    await this.bot.sendMessage(
                        chatId,
                        'You entered the wrong amount, please try again',
                        optionMenu(REFILL_BALANCE_COMMAND)
                    );
                }
            } else {
                await this.redis.set(`action-id-${userId}`, 'refill', 'EX', 300);
                await this.bot.sendMessage(chatId, 'Select the amount to top up', optionMenu(REFILL_BALANCE_COMMAND));
            }
        } catch (error) {
            console.log(error);
            await this.openMenuKeyboard(chatId, userId, 'Refill error, please try again');
        }
    }
    ///////////////////////////////////////////////////////////////////////////////////////

    ///////////////////////////////// РЕФЕРАЛКИ //////////////////////////////////////////
    async goToMenuRefferal(chatId: string, userId: string) {
        await this.bot.sendMessage(chatId, 'Choose what you want to enter', optionMenu(REFERRAL_COMMAND));
    }

    async getReferralCodes(chatId: string, userId: string) {
        try {
            const referralCodes = await this.reqPost({ telegramId: userId }, '/trader/getCodes');
            await this.bot.sendMessage(
                chatId,
                `Your refferalCodes - ${referralCodes.map((code) => code.rc_referralCode).join(', ')}`,
                optionMenu(REFERRAL_COMMAND)
            );
        } catch (error) {
            console.log(error);
            await this.openMenuKeyboard(chatId, userId, 'Error receiving referral codes, please try again');
        }
    }

    async addReferralCodes(chatId: string, userId: string, text?: string) {
        try {
            if ((await this.redis.get(`action-id-${userId}`)) === 'refCode') {
                const answerServer = await this.reqPost({ telegramId: userId, nameCode: text }, '/trader/addCode');
                await this.openMenuKeyboard(
                    chatId,
                    userId,
                    answerServer ? 'Referral code was successful added' : 'Referral code added exist'
                );
                await this.redis.del(`action-id-${userId}`);
            } else {
                await this.redis.set(`action-id-${userId}`, 'refCode', 'EX', 300);
                await this.bot.sendMessage(chatId, 'Enter the new referral code', optionMenu(CANCEL_COMMAND));
            }
        } catch (error) {
            console.log(error);
            await this.openMenuKeyboard(chatId, userId, 'Added new Referral code error, please try again');
        }
    }
    ///////////////////////////////////////////////////////////////////////////////////////

    async changeNickname(chatId: string, userId: string, text?: string) {
        try {
            if ((await this.redis.get(`action-id-${userId}`)) === 'setNick') {
                const answerServer = await this.reqPatch({ telegramId: userId, newNickname: text }, '/trader/setNick');
                await this.openMenuKeyboard(
                    chatId,
                    userId,
                    answerServer ? "You'r nickname was successful change" : 'Nickname is busy'
                );
                await this.redis.del(`action-id-${userId}`);
            } else {
                await this.redis.set(`action-id-${userId}`, 'setNick', 'EX', 300);
                await this.bot.sendMessage(chatId, 'Enter the new nickname', optionMenu(CANCEL_COMMAND));
            }
        } catch (error) {
            console.log(error);
            await this.openMenuKeyboard(
                chatId,
                userId,
                'An error occurred while changing the nickname, please try again'
            );
        }
    }

    async sendMessageToAllSubscribers(chatId: string, userId: string, text?: string) {
        try {
            if ((await this.redis.get(`action-id-${userId}`)) === 'signal') {
                const answerServer = await this.reqPost({ traderTelegramId: userId, message: text }, '/trader/send');
                await this.openMenuKeyboard(
                    chatId,
                    userId,
                    answerServer
                        ? 'Message sent successfully'
                        : 'The message could not be sent, you may not have subscribers'
                );
                await this.redis.del(`action-id-${userId}`);
            } else {
                await this.redis.set(`action-id-${userId}`, 'signal', 'EX', 300);
                await this.bot.sendMessage(chatId, 'Enter the message', optionMenu(CANCEL_COMMAND));
            }
        } catch (error) {
            console.log(error);
            await this.openMenuKeyboard(chatId, userId, 'An error occurred while sending message, please try again');
        }
    }
}
